#include <stdio.h>
#include "csv_parser.h"

typedef enum active_window_t {
    AW_PICKER = 0,
    AW_SHOW,
    AW_LEN,
} active_window_t;

typedef struct csv_parser_app {
    preview_csv_window *pcsv;
    picker_window *pwin;
    active_window_t active_window;
} csv_parser_app;

void free_data(csv_parser_app *app) {
    Data *data = app->pcsv->data;
    for(u64 col_num=0; col_num<data->length; ++col_num) {
        switch (data->types.items[col_num]) {
            case COL_TYPE_STR:
                free_string_column(data->items[col_num]);
                break;
            case COL_TYPE_F32:
                free_f32_column(data->items[col_num]);
                break;
            case COL_TYPE_F64:
                free_f64_column(data->items[col_num]);
                break;
            case COL_TYPE_DATE:
                free_date_column(data->items[col_num]);
                break;
            case COL_TYPE_CATEGORICAL:
                free_categorical_column(data->items[col_num]);
                break;
            default:
                assert(false && "Invalid type in data array!");
                break;
        }
    }
    data->length = 0;
    data->types.length = 0;
}

Vector2 draw_raw_csv(csv_parser_app *app, Vector2 bottom_of_button) {
    u64 num_cols = app->pcsv->raw_csv->items->length;
    // copy csv strings to this context
    if(app->pcsv->transpose_raw_csv) {
        sba2d_free(*app->pcsv->raw_csv_col);
        sba2d_transpose(app->pcsv->raw_csv, app->pcsv->raw_csv_col);
        app->pcsv->transpose_raw_csv = false;
        app->pcsv->convert_columns = true;
    }

    Vector2 text_size = MeasureTextEx(
        GetFontDefault(),
        app->pcsv->raw_csv_col->items->items->items,
        app->pcsv->font_size, app->pcsv->font_size/10.0
    );

    app->pcsv->width = GetScreenWidth()-2*app->pcsv->margin;
    app->pcsv->height = GetScreenHeight()-app->pcsv->margin;
    i32 y_offset = app->pcsv->y + bottom_of_button.y;
    Rectangle rec = {
        .x = app->pcsv->x,
        .y = y_offset,
        .width = app->pcsv->width,
        .height = app->pcsv->height - y_offset - (PREVIEW_LINES-1)*(text_size.y+app->pcsv->margin),
    };
    DrawRectangle(
        rec.x,
        rec.y,
        rec.width,
        rec.height,
        app->pcsv->grid_bg
    );
    i32 col_offset = rec.x + app->pcsv->margin;
    for(u64 col=0; col<num_cols; ++col) {
        i32 row_offset = rec.y + app->pcsv->margin*0.5;
        String_Builder_Array column = app->pcsv->raw_csv_col->items[col];
        i32 max_text_size = 0;
        Vector2 text_size = {0};
        for(u64 row=0; row<column.length; ++row) {
            const char *text = column.items[row].items;
            text_size = MeasureTextEx(GetFontDefault(), text, app->pcsv->font_size, app->pcsv->font_size/10.0);
            DrawText(
                text,
                col_offset,
                row_offset,
                app->pcsv->font_size,
                DARKGRAY
            );
            max_text_size = (max_text_size < text_size.x) ? text_size.x : max_text_size;
            row_offset += text_size.y;
        }
        col_offset += app->pcsv->margin + max_text_size;
    }
    Vector2 ret_corner = {0};
    ret_corner.x = rec.x+rec.width;
    ret_corner.y = rec.y+rec.height;
    return ret_corner;
}

Vector2 draw_header_input(csv_parser_app *app, Vector2 bottom) {
    String_Builder *input = app->pcsv->header_count_str;
    i32 y_offset = app->pcsv->y + bottom.y;
    const char * tooltip = "Header Rows:";
    char max_count[256];
    sprintf(max_count, "%ld", app->pcsv->header_count);
    Vector2 tt_length = MeasureTextEx(
        GetFontDefault(),
        tooltip,
        app->pcsv->font_size, app->pcsv->font_size/10.0
    );
    Vector2 input_length = MeasureTextEx(
        GetFontDefault(),
        max_count,
        app->pcsv->font_size, app->pcsv->font_size/10.0
    );
    Rectangle column_name_box = {
        .x = app->pcsv->margin,
        .y = y_offset,
        .width = tt_length.x + input_length.x + 3.5*app->pcsv->margin,
        .height = tt_length.y + app->pcsv->margin,
    };
    DrawRectangleRec(column_name_box, app->pcsv->grid_bg);
    bool over_column_name_box = CheckCollisionPointRec(GetMousePosition(), column_name_box);
    if(over_column_name_box) {
        DrawText(
            tooltip,
            2*app->pcsv->margin,
            y_offset+0.5*app->pcsv->margin,
            app->pcsv->font_size,
            DARKBLUE
        );
    } else {
        DrawText(
            tooltip,
            2*app->pcsv->margin,
            y_offset+0.5*app->pcsv->margin,
            app->pcsv->font_size,
            DARKGRAY
        );
    }
    Rectangle input_box = {
        .x = tt_length.x + 2.5*app->pcsv->margin,
        .y = y_offset+0.25*app->pcsv->margin,
        .width = input_length.x + app->pcsv->margin,
        .height = column_name_box.height - app->pcsv->margin*0.5,
    };
    DrawRectangleRec(input_box, DARKGRAY);
    DrawText(
        max_count,
        input_box.x + 0.5*app->pcsv->margin,
        input_box.y + 0.5*app->pcsv->margin,
        app->pcsv->font_size,
        LIGHTGRAY
    );
    Vector2 plus_size = MeasureTextEx(
        GetFontDefault(),
        "+",
        app->pcsv->font_size-2, app->pcsv->font_size/10.0
    );
    Vector2 minus_size = MeasureTextEx(
        GetFontDefault(),
        "-",
        app->pcsv->font_size-2, app->pcsv->font_size/10.0
    );
    Vector2 click_box_size = {0};
    click_box_size.x = plus_size.x > minus_size.x ? plus_size.x : minus_size.x;
    click_box_size.y = plus_size.y > minus_size.y ? plus_size.y : minus_size.y;
    Rectangle increment_box = {
        .x = input_box.x + input_box.width,
        .y = column_name_box.y,
        .width = click_box_size.x + app->pcsv->margin,
        .height = column_name_box.height*0.5,
    };
    Rectangle decrement_box = {
        .x = input_box.x + input_box.width,
        .y = increment_box.y + increment_box.height,
        .width = increment_box.width,
        .height = increment_box.height,
    };
    if(CheckCollisionPointRec(GetMousePosition(), increment_box)) {
        DrawRectangleRec(increment_box, DARKGRAY);
        DrawText(
            "+",
            increment_box.x+click_box_size.x*0.5,
            increment_box.y,
            app->pcsv->font_size-2,
            LIGHTGRAY
        );
        if(IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
            app->pcsv->header_count++;
            app->pcsv->convert_columns = true;
        }
    } else {
        DrawRectangleRec(increment_box, LIGHTGRAY);
        DrawText(
            "+",
            increment_box.x+click_box_size.x*0.5,
            increment_box.y,
            app->pcsv->font_size-2,
            DARKGRAY
        );
    }
    if(CheckCollisionPointRec(GetMousePosition(), decrement_box)) {
        DrawRectangleRec(decrement_box, DARKGRAY);
        DrawText(
            "-",
            decrement_box.x+click_box_size.x*0.5,
            decrement_box.y,
            app->pcsv->font_size-2,
            LIGHTGRAY
        );
        if(IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
            app->pcsv->header_count--;
            if(app->pcsv->header_count<0) app->pcsv->header_count = 0;
            app->pcsv->convert_columns = true;
        }
    } else {
        DrawRectangleRec(decrement_box, LIGHTGRAY);
        DrawText(
            "-",
            decrement_box.x+click_box_size.x*0.5,
            decrement_box.y,
            app->pcsv->font_size-2,
            DARKGRAY
        );
    }

    Vector2 ret_bottom = {
        .x = input_box.x + input_box.width,
        .y = input_box.y + input_box.height + app->pcsv->margin*0.5,
    };
    return ret_bottom;
}

Rectangle draw_column_identifier(csv_parser_app *app, Rectangle rec, u64 col_num) {
    // Render & draw column identifier
    const char * max_count = "Column 000";
    Vector2 max_cc_len = MeasureTextEx(
        GetFontDefault(),
        max_count,
        app->pcsv->font_size, app->pcsv->font_size/10.0
    );

    String_Builder tooltip = {0};
    char col_num_str[256];
    sprintf(col_num_str, "%lu", col_num+1);
    sb_append_cstr(&tooltip, "Column ");
    sb_append_cstr(&tooltip, col_num_str);
    sb_append_null(&tooltip);

    Vector2 tt_length = MeasureTextEx(
        GetFontDefault(),
        tooltip.items,
        app->pcsv->font_size, app->pcsv->font_size/10.0
    );

    Rectangle column_name_box = {
        .x = rec.x,
        .y = rec.y,
        .width = max_cc_len.x + app->pcsv->margin + rec.width,
        .height = tt_length.y + app->pcsv->margin + rec.height,
    };
    DrawRectangleRec(column_name_box, app->pcsv->grid_bg);
    bool over_column_name_box = CheckCollisionPointRec(GetMousePosition(), column_name_box);
    Color selector_text_color = DARKGRAY;
    if(over_column_name_box) {
        selector_text_color = BLUE;
    }
    DrawText(
        tooltip.items,
        column_name_box.x + 0.5*app->pcsv->margin,
        column_name_box.y + 0.5*app->pcsv->margin,
        app->pcsv->font_size,
        selector_text_color
    );

    sb_free(tooltip);
    return column_name_box;
}

Rectangle draw_column_preview(csv_parser_app *app, Rectangle start, u64 col_num) {
    String_Builder preview = {0};
    sb_append_cstr(&preview, " (");
    col_num = (col_num < app->pcsv->raw_csv_col->length) ? col_num : 0;
    app->pcsv->header_count = (app->pcsv->header_count < app->pcsv->raw_csv_col->items->length) ? app->pcsv->header_count : app->pcsv->raw_csv_col->items->length-1;
    sb_append_cstr(&preview, app->pcsv->raw_csv_col->items[col_num].items[app->pcsv->header_count].items);
    sb_append_cstr(&preview, ") ");
    sb_append_null(&preview);

    Vector2 prev_sz = MeasureTextEx(
        GetFontDefault(),
        preview.items,
        app->pcsv->font_size, app->pcsv->font_size/10.0
    );
    Rectangle preview_box = {
        .x = start.x + start.width + app->pcsv->margin,
        .y = start.y,
        .width = prev_sz.x + app->pcsv->margin,
        .height = start.height,
    };

    DrawRectangleRec(preview_box, LIGHTGRAY);
    DrawText(
        preview.items,
        preview_box.x + 0.5*app->pcsv->margin,
        preview_box.y + 0.5*app->pcsv->margin,
        app->pcsv->font_size,
        DARKBLUE
    );
    sb_free(preview);
    return preview_box;
}

Rectangle draw_date_format_selector(csv_parser_app *app, Rectangle start, u64 col_num) {
    Vector2 date_format_size = {0};
    String_Builder_Array *hash_sb = app->pcsv->date_format_hash->sba;
    for (u64 i=0; i<hash_sb->length; ++i) {
        Vector2 sz = MeasureTextEx(
            GetFontDefault(),
            hash_sb->items[i].items,
            app->pcsv->font_size,
            app->pcsv->font_size/10.0
        );
        if (sz.x > date_format_size.x) {
            date_format_size.x = sz.x;
        }
        date_format_size.y = sz.y;
    }

    Rectangle format_selector_box = {
        .x = start.x + start.width + app->pcsv->margin,
        .y = start.y,
        .width = date_format_size.x + 2*app->pcsv->margin,
        .height = start.height,
    };

    DrawRectangleRec(format_selector_box, LIGHTGRAY);

    u64 selected_type = app->pcsv->col_date_type->items[col_num];
    const char *selected_text = hash_sb->items[selected_type].items;

    Vector2 sz = MeasureTextEx(
        GetFontDefault(),
        selected_text,
        app->pcsv->font_size, app->pcsv->font_size/10.0
    );
    f32 x_padding = 0.5*(date_format_size.x - sz.x);
    Vector2 text_pos = {
        .x = format_selector_box.x + app->pcsv->margin + x_padding,
        .y = format_selector_box.y + 0.5*app->pcsv->margin,
    };

    bool selected = app->pcsv->column_date_type_selected->items[col_num];
    bool over_date_selector = CheckCollisionPointRec(GetMousePosition(), format_selector_box);
    bool mouse_clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT);
    Color text_color = DARKGRAY;
    if(over_date_selector) {
        text_color = BLUE;
        if(mouse_clicked) {
            for(u64 i=0; i<app->pcsv->column_date_type_selected->length; ++i) {
                app->pcsv->column_date_type_selected->items[i] = false;
            }
            app->pcsv->column_date_type_selected->items[col_num] = true;
            app->pcsv->convert_columns = true;
        }
    }

    DrawTextEx(
        GetFontDefault(),
        selected_text,
        text_pos,
        app->pcsv->font_size,
        app->pcsv->font_size/10.0,
        text_color
    );

    if(selected) {

        Rectangle dropdown_selector_box = {
            .x = format_selector_box.x + format_selector_box.width + app->pcsv->margin,
            .y = format_selector_box.y,
            .width = date_format_size.x + app->pcsv->margin,
            .height = date_format_size.y*hash_sb->length + app->pcsv->margin,
        };
        Rectangle dropdown_selector_border = {
            .x = dropdown_selector_box.x - 0.25*app->pcsv->margin,
            .y = dropdown_selector_box.y - 0.25*app->pcsv->margin,
            .width = dropdown_selector_box.width + 0.5*app->pcsv->margin,
            .height = dropdown_selector_box.height + 0.5*app->pcsv->margin,
        };
        DrawRectangleRec(dropdown_selector_box, LIGHTGRAY);
        DrawRectangleLinesEx(dropdown_selector_border, app->pcsv->margin*0.25, BLACK);

        f32 y_offset = 0;
        for (u64 i=0; i<app->pcsv->date_format_hash->length; ++i) {
            const char *str = app->pcsv->date_format_hash->sba->items[i].items;
            Vector2 sz = MeasureTextEx(
                GetFontDefault(),
                str,
                app->pcsv->font_size, app->pcsv->font_size/10.0
            );
            f32 x_padding = 0.5*(date_format_size.x - sz.x);
            Rectangle text_rec = {
                .x = dropdown_selector_box.x + 0.5*app->pcsv->margin + x_padding,
                .y = dropdown_selector_box.y + y_offset + 0.25*app->pcsv->margin,
                .width = sz.x,
                .height = sz.y + 0.25*app->pcsv->margin
            };
            bool over_text = CheckCollisionPointRec(GetMousePosition(), text_rec);
            Color text_color = DARKGRAY;
            if(over_text) {
                text_color = BLUE;
            }
            DrawText(
                str,
                text_rec.x,
                text_rec.y,
                app->pcsv->font_size,
                text_color
            );
            if(over_text && mouse_clicked) {
                app->pcsv->column_date_type_selected->items[col_num] = false;
                app->pcsv->col_date_type->items[col_num] = i;
                app->pcsv->convert_columns = true;
            }
            y_offset += sz.y + 0.25*app->pcsv->margin;
        }
    }

    return format_selector_box;
}

Rectangle draw_column_type_selector(csv_parser_app *app, Rectangle start, u64 col_num) {
    char *col_type_str[COL_TYPE_LEN] = {
        "String",
        "Float32",
        "Float64",
        "Date",
        "Categorical"
    };
    Vector2 column_selector_size = {0};
    for(u64 i=0; i<COL_TYPE_LEN; ++i) {
        Vector2 sz = MeasureTextEx(
            GetFontDefault(),
            col_type_str[i],
            app->pcsv->font_size, app->pcsv->font_size/10.0
        );
        if (sz.x > column_selector_size.x) column_selector_size.x = sz.x;
        if (sz.y > column_selector_size.y) column_selector_size.y = sz.y;
    }

    Rectangle selector_box = {
        .x = start.x + start.width + app->pcsv->margin,
        .y = start.y,
        .width = column_selector_size.x + app->pcsv->margin,
        .height = column_selector_size.y + app->pcsv->margin,
    };

    Vector2 sz = MeasureTextEx(
        GetFontDefault(),
        col_type_str[0],
        app->pcsv->font_size, app->pcsv->font_size/10.0
    );
    f32 text_height = sz.y*COL_TYPE_LEN;
    f32 box_height = text_height + app->pcsv->margin*2;
    Rectangle dropdown_selector_box = {
        .x = selector_box.x + selector_box.width + app->pcsv->margin,
        .y = selector_box.y,
        .width = selector_box.width,
        .height = box_height
    };
    Rectangle dropdown_selector_border = {
        .x = selector_box.x + selector_box.width + app->pcsv->margin*0.75,
        .y = selector_box.y - app->pcsv->margin*0.25,
        .width = selector_box.width + app->pcsv->margin*0.5,
        .height = box_height + app->pcsv->margin*0.5
    };

    DrawRectangleRec(selector_box, LIGHTGRAY);
    Vector2 mouse_position = GetMousePosition();
    bool over_selector = CheckCollisionPointRec(mouse_position, selector_box);
    bool over_selector_dropdown = CheckCollisionPointRec(mouse_position, dropdown_selector_box);
    bool button_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT);
    bool selected = app->pcsv->column_type_selected->items[col_num];
    u64 ct = app->pcsv->column_types->items[col_num];
    if(over_selector) {
        Vector2 sz = MeasureTextEx(
            GetFontDefault(),
            col_type_str[ct],
            app->pcsv->font_size, app->pcsv->font_size/10.0
        );
        f32 x_padding = 0.5*(column_selector_size.x - sz.x);
        DrawText(
            col_type_str[ct],
            selector_box.x + 0.5*app->pcsv->margin + x_padding,
            selector_box.y + 0.5*app->pcsv->margin,
            app->pcsv->font_size,
            BLUE
        );
        if(button_pressed) {
            for(u64 i=0; i<app->pcsv->column_type_selected->length; ++i) {
                app->pcsv->column_type_selected->items[i] = false;
            }
            app->pcsv->column_type_selected->items[col_num] = true;
            app->pcsv->convert_columns = true;
        }
    } else {
        Vector2 sz = MeasureTextEx(
            GetFontDefault(),
            col_type_str[ct],
            app->pcsv->font_size, app->pcsv->font_size/10.0
        );
        f32 x_padding = 0.5*(column_selector_size.x - sz.x);
        DrawText(
            col_type_str[ct],
            selector_box.x + 0.5*app->pcsv->margin + x_padding,
            selector_box.y + 0.5*app->pcsv->margin,
            app->pcsv->font_size,
            DARKGRAY
        );
    }

    if(selected) {
        DrawRectangleRec(dropdown_selector_box, LIGHTGRAY);
        DrawRectangleLinesEx(dropdown_selector_border, app->pcsv->margin*0.25, BLACK);

        f32 y_offset = 0;
        for (u64 i=0; i<COL_TYPE_LEN; ++i) {
            Vector2 sz = MeasureTextEx(
                GetFontDefault(),
                col_type_str[i],
                app->pcsv->font_size, app->pcsv->font_size/10.0
            );
            f32 x_padding = 0.5*(column_selector_size.x - sz.x);
            Rectangle text_rec = {
                .x = dropdown_selector_box.x + 0.5*app->pcsv->margin + x_padding,
                .y = dropdown_selector_box.y + y_offset + 0.25*app->pcsv->margin,
                .width = sz.x,
                .height = sz.y + 0.25*app->pcsv->margin
            };
            bool over_text = CheckCollisionPointRec(GetMousePosition(), text_rec);
            Color text_color = DARKGRAY;
            if(over_text) {
                text_color = BLUE;
            }
            DrawText(
                col_type_str[i],
                text_rec.x,
                text_rec.y,
                app->pcsv->font_size,
                text_color
            );
            if(over_text && button_pressed) {
                app->pcsv->column_type_selected->items[col_num] = false;
                app->pcsv->column_types->items[col_num] = i;
                app->pcsv->convert_columns = true;
            }
            y_offset += sz.y + 0.25*app->pcsv->margin;
        }
    }

    return selector_box;
}

Vector2 draw_column_convert(csv_parser_app *app, Vector2 bottom, u64 col_num) {
    i32 y_offset = app->pcsv->y + bottom.y;
    Rectangle start_box = {
        .x = app->pcsv->margin,
        .y = y_offset,
        .width = 0,
        .height = 0,
    };

    Rectangle column_name_box = draw_column_identifier(app, start_box, col_num);
    Rectangle preview_box = draw_column_preview(app, column_name_box, col_num);
    Rectangle selector_box = draw_column_type_selector(app, preview_box, col_num);
    bool selected = app->pcsv->column_type_selected->items[col_num];
    if(app->pcsv->column_types->items[col_num] == COL_TYPE_DATE && !selected) {
        draw_date_format_selector(app, selector_box, col_num);
    }

    Vector2 ret_bottom = {
        .x = preview_box.x + preview_box.width,
        .y = preview_box.y + preview_box.height,
    };

    return ret_bottom;
}

void parse_csv_to_cols(csv_parser_app *app) {
    // read entire file in
    // TODO: Maybe work in 1GB chunks???
    app->pcsv->csv_raw->length = 0;
    sb_read_entire_file(app->pwin->csv_path_view.data, app->pcsv->csv_raw);

    app->pcsv->csv_raw_rows.data = app->pcsv->csv_raw->items;
    app->pcsv->csv_raw_rows.length = app->pcsv->csv_raw->length;
    String_Builder_Array row = {0};
    String_Builder sb = {0};
    u64 total_bytes = 0;
    sba2d_free(*app->pcsv->raw_csv_full);
    while(app->pcsv->csv_raw_rows.length > 0) {
        String_View line = sv_chop_by_delim(&app->pcsv->csv_raw_rows, '\n');
        u64 line_bytes = 0;
        u64 length = line.length;
        while(line_bytes < length && length >=1 ) {
            String_View col_sv = sv_chop_by_delim(&line, ',');
            sb_append(&sb, col_sv.data, col_sv.length);
            sb_append_null(&sb);
            sba_append(&row, &sb);
            sb.length = 0;
            line_bytes += col_sv.length;
            // decrement to account for the consumed delimiter
            length--;
        }
        total_bytes += line_bytes;
        sba2d_append(app->pcsv->raw_csv_full, &row);
        sba_free(row);
    }
    sb_free(sb);
    sba_free(row);
    reset_free(row);

    // convert string csv row to col-major
    sba2d_free(*app->pcsv->raw_csv_col_full);
    sba2d_transpose(app->pcsv->raw_csv_full, app->pcsv->raw_csv_col_full);
    sba2d_free(*app->pcsv->raw_csv_full);
}

void parse_csv_raw(csv_parser_app *app) {
    app->pcsv->csv_raw->length = 0;
    sb_read_entire_file(app->pwin->csv_path_view.data, app->pcsv->csv_raw);

    app->pcsv->csv_raw_rows.data = app->pcsv->csv_raw->items;
    app->pcsv->csv_raw_rows.length = app->pcsv->csv_raw->length;
    String_Builder_Array row = {0};
    String_Builder sb = {0};
    u64 total_bytes = 0;
    sba2d_free(*app->pcsv->raw_csv);
    for(u64 i=0; i<PREVIEW_LINES; ++i) {
        String_View line = sv_chop_by_delim(&app->pcsv->csv_raw_rows, '\n');
        u64 line_bytes = 0;
        u64 length = line.length;
        while(line_bytes < length && length >=1 ) {
            String_View col_sv = sv_chop_by_delim(&line, ',');
            sb_append(&sb, col_sv.data, col_sv.length);
            sb_append_null(&sb);
            sba_append(&row, &sb);
            sb.length = 0;
            line_bytes += col_sv.length;
            // decrement to account for the consumed delimiter
            length--;
        }
        total_bytes += line_bytes;
        sba2d_append(app->pcsv->raw_csv, &row);
        sba_free(row);
    }
    sba_free(row); reset_free(row);
    sb_free(sb);
    app->pwin->new_file_picked = false;
    app->pcsv->transpose_raw_csv = true;
}

void convert_col_strs(csv_parser_app *app) {
    if(app->pcsv->convert_columns) {
        parse_csv_to_cols(app);
        free_data(app);
        for(u64 col_num=0; col_num<app->pcsv->column_types->length; ++col_num) {
            u64 ct = app->pcsv->column_types->items[col_num];
            String_Builder_Array col = app->pcsv->raw_csv_col_full->items[col_num];
            switch (ct) {
                case COL_TYPE_STR:
                    {
                        StrColumn *new_array;
                        init_array(new_array);
                        new_array->type = COL_TYPE_STR;
                        sba_to_str(&col, new_array, app->pcsv->header_count);
                        dyn_array_append(app->pcsv->data, new_array);
                        dyn_array_append(&app->pcsv->data->types, new_array->type);
                    }
                    break;
                case COL_TYPE_F32:
                    {
                        F32Column *new_array;
                        init_array(new_array);
                        new_array->type = COL_TYPE_F32;
                        sba_to_f32(&col, new_array, app->pcsv->header_count);
                        dyn_array_append(app->pcsv->data, new_array);
                        dyn_array_append(&app->pcsv->data->types, new_array->type);
                    }
                    break;
                case COL_TYPE_F64:
                    {
                        F64Column *new_array;
                        init_array(new_array);
                        new_array->type = COL_TYPE_F64;
                        sba_to_f64(&col, new_array, app->pcsv->header_count);
                        dyn_array_append(app->pcsv->data, new_array);
                        dyn_array_append(&app->pcsv->data->types, new_array->type);
                    }
                    break;
                case COL_TYPE_DATE:
                    {
                        DateColumn *new_array;
                        init_array(new_array);
                        new_array->type = COL_TYPE_DATE;

                        String_Builder_Array *hash_sb = app->pcsv->date_format_hash->sba;
                        u64 selected_type = app->pcsv->col_date_type->items[col_num];
                        const char *selected_text = hash_sb->items[selected_type].items;
                        sb_append_cstr(&new_array->format, selected_text);
                        sb_append_null(&new_array->format);
                        sb_append_cstr(&new_array->display, "%F");
                        sb_append_null(&new_array->display);

                        sba_to_date(&col, new_array, app->pcsv->header_count);
                        dyn_array_append(app->pcsv->data, new_array);
                        dyn_array_append(&app->pcsv->data->types, new_array->type);
                    }
                    break;
                case COL_TYPE_CATEGORICAL:
                    {
                        CategoricalColumn *new_array;
                        init_array(new_array);
                        new_array->type = COL_TYPE_CATEGORICAL;
                        sba_to_categorical(&col, new_array, app->pcsv->header_count);
                        dyn_array_append(app->pcsv->data, new_array);
                        dyn_array_append(&app->pcsv->data->types, new_array->type);
                    }
                    break;
                default:
                    assert(false && "Encountered invalid column type!");
                    break;
            }
        }
        assert((app->pcsv->data->length == app->pcsv->data->types.length) && "Failed to parse data! Array length and type length mismatch.");
        app->pcsv->convert_columns = false;
        sba2d_free(*app->pcsv->raw_csv_col_full);
        app->pcsv->convert_columns = false;
    }
}

void show_csv(csv_parser_app *app) {
    DrawText("Selected:", app->pwin->x, app->pwin->y, app->pwin->font_size, LIGHTGRAY);
    app->pwin->width = GetScreenWidth()-2*app->pwin->margin;
    DrawRectangle(
        app->pwin->x, app->pwin->y+app->pwin->font_size+app->pwin->margin, app->pwin->width, app->pwin->font_size, LIGHTGRAY
    );
    DrawText(
        app->pwin->csv_path_view.data,
        app->pwin->x,
        app->pwin->y+app->pwin->font_size+app->pwin->margin,
        app->pwin->font_size,
        DARKGRAY
    );
    Vector2 bottom = {0};
    { // draw Pick File button
        const char *text = "Pick File";
        Vector2 text_size = MeasureTextEx(GetFontDefault(), text, app->pcsv->font_size, app->pcsv->font_size/10.0);
        Rectangle rec = {
            .x = app->pwin->x,
            .y = app->pwin->y + 2*(app->pwin->margin + app->pwin->font_size),
            .width = text_size.x + 2*app->pwin->margin,
            .height = text_size.y + app->pwin->margin,
        };
        bottom.x = rec.x + rec.width;
        bottom.y = rec.y + rec.height;
        Color rect_color = DARKBLUE;
        Color text_color = LIGHTGRAY;
        if (CheckCollisionPointRec(GetMousePosition(), rec)) {
            rect_color = BLUE;
            text_color = BLACK;
            if(IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
                app->active_window = AW_PICKER;
                app->pwin->new_file_picked = false;
        }
        DrawRectangle(
            rec.x,
            rec.y,
            rec.width,
            rec.height,
            rect_color
        );
        DrawText(
            text,
            rec.x+app->pwin->margin,
            rec.y+app->pwin->margin*0.5,
            app->pwin->font_size,
            text_color
        );
    }

    if(app->pwin->new_file_picked)
        parse_csv_raw(app);
    bottom = draw_raw_csv(app, bottom);
    bottom = draw_header_input(app, bottom);
    if(app->pcsv->convert_columns || app->pcsv->column_types->length != app->pcsv->raw_csv_col->length) {
        app->pcsv->column_types->length = 0;
        app->pcsv->column_type_selected->length = 0;
        app->pcsv->column_date_type_selected->length = 0;
        app->pcsv->col_date_type->length = 0;
        for(u64 i=0; i<app->pcsv->raw_csv_col->length; ++i) {
            dyn_array_append(app->pcsv->column_types, COL_TYPE_STR);
            dyn_array_append(app->pcsv->column_type_selected, false);
            dyn_array_append(app->pcsv->column_date_type_selected, false);
            dyn_array_append(app->pcsv->col_date_type, 0);
        }
    }
    for(u64 i=0; i<app->pcsv->raw_csv_col->length; ++i) {
        bottom = draw_column_convert(app, bottom, i);
    }
    convert_col_strs(app);

}

void file_picker(csv_parser_app *app) {
    FilePathList fpl = {0};
    app->pwin->cwd->length = 0;
    sb_append_cstr(app->pwin->cwd, GetWorkingDirectory());
    sb_append_null(app->pwin->cwd);
    app->pcsv->cwd_view.data = app->pwin->cwd->items;
    app->pcsv->cwd_view.length = app->pwin->cwd->length;
    // Get directory files
    fpl = LoadDirectoryFilesEx(app->pcsv->cwd_view.data, ".csv", 0);
    sba_free(*app->pwin->csv_paths);
    // Copy paths to memory we control
    for (u32 i=0; i<fpl.count; ++i) {
        sba_append_cstr(app->pwin->csv_paths, fpl.paths[i]);
    }
    // remove fpl data from raylib arena
    UnloadDirectoryFiles(fpl);

    app->pwin->width = GetScreenWidth()-2*app->pwin->margin;
    app->pwin->height = GetScreenHeight()-2*app->pwin->margin;

    DrawRectangle(
        app->pwin->x, app->pwin->y, app->pwin->width, app->pwin->height, LIGHTGRAY
    );
    u32 offset = app->pwin->margin;
    Rectangle rec = {0};
    for(u32 i=0; i<app->pwin->csv_paths->length; ++i) {
        const char *path = app->pwin->csv_paths->items[i].items;
        rec.x = app->pwin->x + 5;
        rec.y = app->pwin->y + offset;
        rec.width = app->pwin->width - app->pwin->margin;
        rec.height = app->pwin->font_size;
        if (CheckCollisionPointRec(GetMousePosition(), rec)) {
            DrawRectangle(rec.x, rec.y, rec.width, rec.height, app->pwin->list_color);
            DrawText(
                path,
                rec.x,
                rec.y,
                app->pwin->font_size,
                LIGHTGRAY
            );
            if(CheckCollisionPointRec(GetMousePosition(), rec) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
                memset(app->pwin->csv_path->items, '\0', app->pwin->csv_path->length);
                app->pwin->csv_path->length = 0;
                sb_append_cstr(app->pwin->csv_path, path);
                sb_append_null(app->pwin->csv_path);
                app->pwin->csv_path_view.data = app->pwin->csv_path->items;
                app->pwin->csv_path_view.length = app->pwin->csv_path->length;
                app->active_window = AW_SHOW;
                app->pwin->new_file_picked = true;
                app->pcsv->transpose_raw_csv = true;
            }
        } else {
            DrawText(
                path,
                app->pwin->x + app->pwin->margin*0.5,
                app->pwin->y + offset,
                app->pwin->font_size,
                app->pwin->list_color
            );
        }
        offset += app->pwin->font_size;
    }
}

void setup_csv_parser(csv_parser_app *app) {
    String_Builder *cwd;
    init_array(cwd);
    
    String_Builder *csv_path;
    init_array(csv_path);

    String_Builder_Array *csv_paths;
    init_array(csv_paths);

    picker_window *tmp_pwin;
    init_array(tmp_pwin);

    tmp_pwin->x = MARGIN;
    tmp_pwin->y = MARGIN;
    tmp_pwin->width = SCREEN_WIDTH - 2*MARGIN;
    tmp_pwin->height = 160;
    tmp_pwin->margin = MARGIN;
    tmp_pwin->font_size = FONT_SIZE;
    tmp_pwin->list_color = DARKGRAY;
    tmp_pwin->pcwd = NULL;
    tmp_pwin->cwd = cwd;
    tmp_pwin->csv_path = csv_path;
    tmp_pwin->csv_paths = csv_paths;
    tmp_pwin->new_file_picked = false;

    String_Builder *csv_raw;
    init_array(csv_raw);

    String_Builder_Array_2D *raw_csv;
    init_array(raw_csv);

    String_Builder_Array_2D *raw_csv_full;
    init_array(raw_csv_full);

    String_Builder_Array_2D *raw_csv_col;
    init_array(raw_csv_col);

    String_Builder_Array_2D *raw_csv_col_full;
    init_array(raw_csv_col_full);

    String_Builder *header_count_str;
    init_array(header_count_str);

    ColTypes *column_types;
    init_array(column_types);

    ColSelected *column_type_selected;
    init_array(column_type_selected);

    ColSelected *column_date_type_selected;
    init_array(column_date_type_selected);

    HashList *date_format_hash;
    init_array(date_format_hash);

    String_Builder_Array *date_formats;
    init_array(date_formats);

    ColDateType *col_date_type;
    init_array(col_date_type);

    Data *data;
    init_array(data);

    String_Builder_Array *hl_sb;
    init_array(hl_sb);

    HashArray *hl_ha;
    init_array(hl_ha);

    preview_csv_window* tmp_pcsv;
    init_array(tmp_pcsv);

    tmp_pcsv->x = MARGIN;
    tmp_pcsv->y = MARGIN;
    tmp_pcsv->width = SCREEN_WIDTH - 2*MARGIN;
    tmp_pcsv->height = SCREEN_HEIGHT;
    tmp_pcsv->margin = MARGIN;
    tmp_pcsv->font_size = FONT_SIZE;
    tmp_pcsv->grid_bg = LIGHTGRAY;
    tmp_pcsv->grid_lines = DARKGRAY;
    tmp_pcsv->csv_raw = csv_raw;
    tmp_pcsv->raw_csv = raw_csv;
    tmp_pcsv->raw_csv_full = raw_csv_full;
    tmp_pcsv->raw_csv_col = raw_csv_col;
    tmp_pcsv->raw_csv_col_full = raw_csv_col_full;
    tmp_pcsv->header_count_str = header_count_str;
    tmp_pcsv->column_types = column_types;
    tmp_pcsv->column_type_selected = column_type_selected;
    tmp_pcsv->column_date_type_selected = column_date_type_selected;
    tmp_pcsv->date_format_hash = date_format_hash;
    tmp_pcsv->date_formats = date_formats;
    tmp_pcsv->col_date_type = col_date_type;
    tmp_pcsv->data = data;
    tmp_pcsv->hl_sb = hl_sb;
    tmp_pcsv->hl_ha = hl_ha;
    tmp_pcsv->transpose_raw_csv = true;
    tmp_pcsv->convert_columns = true;

    app->pcsv = tmp_pcsv;
    app->pwin = tmp_pwin;
    app->active_window = AW_PICKER;

    sba_append_cstr(app->pcsv->date_formats, "%m/%d/%Y");
    sba_append_cstr(app->pcsv->date_formats, "%d/%m/%Y");
    sba_append_cstr(app->pcsv->date_formats, "%m-%d-%Y");
    sba_append_cstr(app->pcsv->date_formats, "%d-%m-%Y");
    init_hash_list(
        app->pcsv->date_format_hash,
        app->pcsv->date_formats,
        app->pcsv->hl_sb,
        app->pcsv->hl_ha
    );
}

void free_csv_parser(csv_parser_app *app) {
    // free pwin
    picker_window *pwin = app->pwin;
    sb_free(*pwin->cwd);
    free(pwin->cwd);
    sb_free(*pwin->csv_path);
    free(pwin->csv_path);
    sba_free(*pwin->csv_paths); reset_free(*pwin->csv_paths);
    free(pwin->csv_paths);
    free(pwin);

    // free pcsv
    preview_csv_window *pcsv = app->pcsv;
    sb_free(*pcsv->csv_raw);
    free(pcsv->csv_raw);
    sba2d_free(*pcsv->raw_csv); reset_free(*pcsv->raw_csv);
    free(pcsv->raw_csv);
    sba2d_free(*pcsv->raw_csv_full); reset_free(*pcsv->raw_csv_full);
    free(pcsv->raw_csv_full);
    sba2d_free(*pcsv->raw_csv_col); reset_free(*pcsv->raw_csv_col);
    free(pcsv->raw_csv_col);
    sba2d_free(*pcsv->raw_csv_col_full); reset_free(*pcsv->raw_csv_col_full);
    free(pcsv->raw_csv_col_full);
    sb_free(*pcsv->header_count_str);
    free(pcsv->header_count_str);
    reset_free(*pcsv->column_types);
    free(pcsv->column_types);
    reset_free(*pcsv->column_type_selected);
    free(pcsv->column_type_selected);
    reset_free(*pcsv->column_date_type_selected);
    free(pcsv->column_date_type_selected);
    free_hash_list(*pcsv->date_format_hash);
    free(pcsv->date_format_hash);
    sba_free(*pcsv->date_formats); reset_free(*pcsv->date_formats);
    free(pcsv->date_formats);
    reset_free(*pcsv->col_date_type);
    free(pcsv->col_date_type);
    free_data(app);
    reset_free(pcsv->data->types);
    reset_free(*pcsv->data);
    free(pcsv->data);
    free(pcsv);
}

int main(void) {
    csv_parser_app app = {0};
    setup_csv_parser(&app);

    SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_ALWAYS_RUN);
    InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "CSV File Reader");
    SetTargetFPS(TARGET_FPS);

    while (!WindowShouldClose()) {
        BeginDrawing();
        ClearBackground(DARKGRAY);
        switch (app.active_window) {
            case AW_PICKER: {
                file_picker(&app);
                break;
            }
            case AW_SHOW: {
                show_csv(&app);
                break;
            }
            default: {
                app.active_window = AW_PICKER;
                break;
            }
        }
        f32 frame_time = GetFrameTime();
        char fpsstr[256];
        sprintf(fpsstr, "FPS: %.3g", 1/frame_time);
        Vector2 text_size = MeasureTextEx(
            GetFontDefault(),
            fpsstr,
            app.pcsv->font_size, app.pcsv->font_size/10.0
        );
        DrawText(fpsstr, GetScreenWidth() - text_size.x - app.pcsv->margin, app.pcsv->margin, app.pcsv->font_size, WHITE);
        EndDrawing();
    }
    CloseWindow();
    free_csv_parser(&app);
    return 0;
}
